/**
 *
 */
package com.honeybees.framework.common.util.twitter.snowflake;

import java.net.InetAddress;

/**
 * 生成全局UID - snowflake IdWorker
 *
 * <p>在分布式系统中，需要生成全局UID的场合还是比较多的，twitter的snowflake解决了这种需求， 实现也还是很简单的，除去配置信息，核心代码就是毫秒级时间41位+机器ID
 * 10位+毫秒内序列12位。 该项目地址为：https://github.com/twitter/snowflake 是用Scala实现的。
 * </p>
 * <p>Python版详见开源项目 https://github.com/erans/pysnowflake。</p>
 * <p>https://github.com/twitter/snowflake/releases</p>
 *
 * @author ijymLee
 * @since 2014-12-13 14:28:11
 */
public final class IdWorker {

    /**
     * 根据具体机器环境提供
     */
    private final long workerId;
    /**
     * 滤波器；使时间变小，生成的总位数变小，一旦确定不能变动。
     * <p>
     * 1361753741828L
     */
    private final static long twepoch = 1361753741828L;
    private long sequence = 0L;
    /**
     * 机器标识位数
     */
    private final static long workerIdBits = 10L;
    /**
     * 机器ID最大值
     */
    private final static long maxWorkerId = ~(-1L << workerIdBits); // -1L ^ (-1L << workerIdBits)
    /**
     * 毫秒内自增位
     */
    private final static long sequenceBits = 12L;
    /**
     * 机器ID偏左移12位
     */
    private final static long workerIdShift = sequenceBits;
    /**
     * 时间毫秒左移22位
     */
    private final static long timestampLeftShift = sequenceBits + workerIdBits;
    private final static long sequenceMask = ~(-1L << sequenceBits); // -1L ^ (-1L << sequenceBits)

    private long lastTimestamp = -1L;
    /**
     * 根据主机ID获取机器码
     */
    private static IdWorker worker = new IdWorker();

    /**
     * 请调用 {@link #getId()} 方法得到ID。
     */
    private IdWorker() {
        this.workerId = getAddress() % (maxWorkerId + 1);
        // System.out.println("workerId: " + workerId);
    }

    /**
     * <dl>
     * <dt><b> 得到18位长的数字ID </b></dt>
     * <p>
     * <dd>功能描述</dd>
     * </dl>
     *
     * @return ID 356624588167303168
     * @author 李远明
     * @version 2015-11-06 11:15:23 改为返回 String
     * @see #nextId()
     * @since 2014-12-13 14:28:11
     */
    public static String getId() {
        long id = worker.nextId();
        return String.valueOf(id);
    }

    /**
     * <dl>
     * <dt><b> 标题 </b></dt>
     * <p>
     * <dd>功能描述</dd>
     * </dl>
     *
     * @return
     * @author 李远明
     * @see #getId()
     */
    private synchronized long nextId() {
        long timestamp = this.timeGen();
        if (this.lastTimestamp == timestamp) { // 当前毫秒内，则+1
            this.sequence = (this.sequence + 1) & sequenceMask;
            if (this.sequence == 0) { // 当前毫秒内计数满了，则等待下一秒
                // System.out.println("###########" + sequenceMask);//等待下一毫秒
                timestamp = this.tilNextMillis(this.lastTimestamp);
            }
        } else {
            this.sequence = 0;
        }
        if (timestamp < this.lastTimestamp) {
            try {
                throw new Exception(String.format("Clock moved backwards.Refusing to generate id for %d milliseconds",
                        this.lastTimestamp - timestamp));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        this.lastTimestamp = timestamp;

        // ID偏移组合生成最终的ID，并返回ID
        long nextId = ((timestamp - twepoch << timestampLeftShift)) | (this.workerId << IdWorker.workerIdShift)
                | (this.sequence);
        // System.out.println("timestamp:" + timestamp + ",timestampLeftShift:"
        // + timestampLeftShift + ",nextId:" + nextId + ",workerId:"
        // + workerId + ",sequence:" + sequence);
        return nextId;
    }

    /**
     * <dl>
     * <dt><b> 等待下一个毫秒的到来 </b></dt>
     * <p>
     * <dd></dd>
     * </dl>
     *
     * @param lastTimestamp
     * @return
     */
    private long tilNextMillis(final long lastTimestamp) {
        long timestamp = this.timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = this.timeGen();
        }
        return timestamp;
    }

    private long timeGen() {
        return System.currentTimeMillis();
    }

    private static long getAddress() {
        try {
            String ip = InetAddress.getLocalHost().getHostAddress();
            // System.out.println("currentIpAddress: " + ip);
            String[] digits = ip.split("\\.");
            StringBuilder hardware = new StringBuilder();
            for (String digit : digits) {
                hardware.append(digit);
            }
            // System.out.println("hardware: " + hardware);
            long retLong = Long.parseLong(hardware.toString());
            return retLong;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 2L;
    }

    public static void main(final String[] args) {
        for (int i = 0; i < 14; i++) {
            System.out.println(getId());
        }
    }

}
